//==============================================================================
// Copyright (c) 2008-2013 Niall Ryan. All Rights Reserved.
//==============================================================================

#include <gtest/gtest.h>

#include "hydra/hashmaps/StripedHashMap.h"
#include "hydra/hashmaps/RefinableHashMap.h"
#include "hydra/hashmaps/LockFreeHashMap.h"

using namespace Hydra;

template<typename HashMap>
class HashMapTest : public ::testing::Test
{
protected:
	static const int NUM_THREADS = 4;
	static const int NUM_ITERATIONS = 1000;
	static void InsertErase(HashMapTest* test, int threadId)
	{
		int baseIndex = 1000*threadId;
		int numElements = 100;

		for( int iteration=0; iteration<NUM_ITERATIONS; ++iteration )
		{
			//insert
			for( int i=baseIndex; i<baseIndex+numElements; ++i )
			{
				if( !test->m_hashMap.insert(i, i+1) )
					++test->m_counters[threadId];
			}

			//find
			for( int i=baseIndex; i<baseIndex+numElements; ++i )
			{
				int value;
				if( !test->m_hashMap.find(i, &value) )
					++test->m_counters[threadId];
				if( value != i+1 )
					++test->m_counters[threadId];
			}

			//erase
			for( int i=baseIndex; i<baseIndex+numElements; ++i )
			{
				if( !test->m_hashMap.erase(i) )
					++test->m_counters[threadId];
			}
		}
	}

	HashMap m_hashMap;
	Atomic<int> m_counters[NUM_THREADS];
};


typedef ::testing::Types<
    StripedHashMap<int, int, HashUtil::HashFunction<int>, HashUtil::EqualTo<int>, TTASLock, 16>,
    StripedHashMap<int, int, HashUtil::HashFunction<int>, HashUtil::EqualTo<int>, TTASLock, 64>,
    RefinableHashMap<int, int>,
    LockFreeHashMap<int, int>
    > HashMapTestTypes;
TYPED_TEST_CASE(HashMapTest, HashMapTestTypes);


TYPED_TEST(HashMapTest, Stuff)
{
	//TEST_TIMER;

	//
	//single threaded functionality tests
	//
	int value;

	EXPECT_TRUE(m_hashMap.isEmpty());
	EXPECT_TRUE(m_hashMap.getSize() == 0);
	EXPECT_TRUE(m_hashMap.insert(10, 20));
	EXPECT_TRUE(!m_hashMap.isEmpty());
	EXPECT_TRUE(m_hashMap.getSize() == 1);
	EXPECT_TRUE(m_hashMap.contains(10));
	EXPECT_TRUE(m_hashMap.find(10, &value));
	EXPECT_TRUE(value == 20);
	EXPECT_TRUE(!m_hashMap.contains(11));
	EXPECT_TRUE(!m_hashMap.find(11, &value));
	EXPECT_TRUE(m_hashMap.insert(10+256, 30)); //collision
	EXPECT_TRUE(m_hashMap.insert(11, 40));
	EXPECT_TRUE(m_hashMap.getSize() == 3);
	EXPECT_TRUE(m_hashMap.find(10, &value));
	EXPECT_TRUE(value == 20);
	EXPECT_TRUE(m_hashMap.find(11, &value));
	EXPECT_TRUE(value == 40);
	EXPECT_TRUE(m_hashMap.find(266, &value));
	EXPECT_TRUE(value == 30);
	EXPECT_TRUE(!m_hashMap.erase(42));
	EXPECT_TRUE(m_hashMap.getSize() == 3);
	EXPECT_TRUE(m_hashMap.erase(10));
	EXPECT_TRUE(m_hashMap.getSize() == 2);
	EXPECT_TRUE(m_hashMap.erase(266));
	EXPECT_TRUE(m_hashMap.erase(11));
	EXPECT_TRUE(m_hashMap.getSize() == 0);
	EXPECT_TRUE(m_hashMap.isEmpty());

	//
	//multithread tests
	//
	Thread threads[NUM_THREADS];
	{
		for( int i=0; i<NUM_THREADS; ++i )
		{
			threads[i] = Thread(&InsertErase, this, i);
			m_counters[i] = 0;
		}
		for( int i=0; i<NUM_THREADS; ++i )
			threads[i].Start();
		for( int i=0; i<NUM_THREADS; ++i )
			threads[i].Join();
		for( int i=0; i<NUM_THREADS; ++i )
			EXPECT_TRUE(m_counters[i]==0);
	}
}

